#include <linux/init.h>         //包含宏定义得头文件
#include <linux/module.h>       //包含初始化加载模块得头文件
#include <linux/kdev_t.h>       //处理设备号宏定义的头文件
#include <linux/miscdevice.h>   //杂项设备头文件
#include <linux/fs.h>           //文件操作集
#include <linux/uaccess.h>      //copy_to_user函数的文件
#include <linux/io.h>           //ioermap等函数的头文件
#include <linux/platform_device.h>       //平台总线模型文件
#include <linux/of.h>           //设备树操作函数操作集
#include <linux/of_address.h>   //of_iomap
#include <linux/gpio.h>         //gpio子系统的使用需要的头文件
#include <linux/of_gpio.h>      //操作gpio子系统相关的函数使用的头文件 
#include <linux/interrupt.h>    //该函数定义了中断处理函数以及中断下文tasklet结构体
#include <linux/of_irq.h>       //向内核注册中断号的函数头文件

#include <linux/workqueue.h>       //工作队列头文件 
// #include <linux/cdevdevice.h>   //字符设备头文件
/*包含头文件*/
/*************实现中断上下文实验tasklet以及等待队列、工作队列********************
中断
*（1）在设备运行中我们会穿插某些紧急的任务来运行其中过程包括(中断请求、中断响应、
保护现场、中断处理、恢复现场、中断返回、中断屏蔽)之前16例程完成了中断的上文
*（2）中断下文由tasklet来实现，tasklet是一种特殊的软中断，处理中断上下文的机制
*还有工作队列和中断
*（3）如何使用tasklet设计中断上下文？(中断发生，上文处理紧急的事情，调用tasklet)
*（4）tasklet设计中断下文步骤：定义tasklet结构体、动态初始化tasklet、编写tasklet
* 绑定的函数、在中断上文调用tasklet、卸载模块的时候删除tasklet共五个步骤
*（5）等待队列使用的是结构体wait_queue_head_t来表示
*（6）工作队列介绍：工作队列是实现中断下文机制之一，将工作推后执行的形式与tasklet
* 相同，但是tasklet不可以休眠工作队列是可以休眠的(可以用于处理复杂与耗时的工作)
* (7)工作队列原理：linux启动创建内核线程，创建后处于sleep状态，然后这线程会在队列中获取是否由任务
* 如果有则执行，没有就休眠
* (8)共享工作队列：linux自动创建，多种不同的事件处理
* (9)处理步骤：动态初始化结构体DECLARE_WORK(test,func)、调度工作schedule_work挂到cpu工作队列中
、在上次任务没完成多次调度是无效的
* (9)注意点：查看设备树是否有添加上节点使用cd /proc/device-tree后使用ls查看，cat compatible可以查看属性
* (10)调试方法:cat /proc/irq/4/spurious(可以查看打印) top查看进程占用内存
***********************************************************************/

/*宏定义*/


/*全局变量*/
struct device_node*test_device_node;//获取设备树节点的变量
struct property*property_test;      //

struct tasklet_struct key_tasklet;  //定义中断下文结构体
//int beep_gpio=0;                  //用于获取GPIO编号
int gpio_num;                   //中断实验获取gpio编号
int irq_num;                    //获取中断号
int value=0;                    //用来模拟管脚的状态
struct work_struct key_work;    //定义中断下文结构体

/*文件操作集的结构体*/
int cdev_open(struct inode*inode,struct file * file)
{
    printk("hello cdev opnen\n");
    return 0;
}

int cdev_release(struct inode*inode,struct file * file)
{
    printk("hello cdev cdev_release byb byb!\n");
    return 0;
}

int cdev_read(struct file * file,char _user * ubuf,size_t size,loff_t*loff_t)
{
    wait_event_interruptble(key_wq,wq_flags);//执行阻塞任务
    char kbuf[64]="hello";
    if(copy_to_user(ubuf,kbuf,strlen(kbuf)!=0))//拷贝信息到应用层
    {
        printk("copy_to_user error!\n");
        return -1;
    }

    printk("hello cdev_read file!\n");
    wq_flags=0;//标志位清零
    return 0;
}

//获取到应用层的数据到内核层在改函数进行io的控制操作
int cdev_write(struct file * file,char _user * ubuf,size_t size,loff_t*loff_t)
{
     char kbuf[64]={0};
    if(copy_form_user(kbuf,ubuf,size)!=0)//从应用层拷贝数据到内核层
    {
         printk("copy_form_user error!\n");
        return -1;
    }
    printk("copy_form_user %s!\n",kbuf);
    if(kbuf[0]==1)

    return 0;
}

struct file_operations cdevdev_fops={
    .owner = THIS_MODULE,
    .open  = cdev_open,
    .release = cdev_release,
    .read = cdev_read,
    .write = cdev_write,
    // .unlocked.ioctl = misc_ioctl
};

/*描述杂项设备的结构体*/
struct miscdevice misc_dev={
.minor = MISC_DYNAMIC_MINOR, //
.name  ="hello_misc",       //设备节点的名称查看dev
.fops  =&misc_fops1
};

/**************中断等相关的处理函数包括上文与tasklet、等待队列***************/


/*中断下文初始化tasklet具体实现函数，需要在中断处理函数处调用*/
void tasklet_test(unsigned long data)
{
    int i=100;
    printk("i%d\n",i);
    while(i--)
        printk("i%d\n",i);
}

/*中断处理函数*/
irq_handler_t test_key_IRQ(int irq,void *args)
{
    printk("helle irq\n");
   // tasklet_schedule(&key_tasklet);//在中断中启动中断下文
    schedule_work(&work_struct);
    printk("end!\n");
    return IRQ_HANDLED;
}
/******************end***********************/


/*************platform_driver内容处理************/
int beep_probe(struct platform_device*pdev)
{
    int ret;
    int irq_reg;
    int misc_reg;
    printk("beep_probe\n");
/*获取设备树信息的方法手段*/
    //方法一：直接获取设备树的节点信息
    printk("node name is %s\n",pdev->dev.of_node->name);
    /*方法二：先获取到设备树的设备节点后续再处理*/
    /*配置中断步骤一：获取到设备树的节点*/  
    test_device_node=of_find_node_by_path("/test_key");//获取节点的信息，/test取绝对路径
        if(test_device_node==NULL)
        {
            printk("test_device_node error!\n");
            return -1;
        }
    printk("test_device_node name is %s",test_device_node->name);

/*使用pinctrl-gpio子系统来处理gpio以及中断事物*/
    /*配置中断步骤二：获得GPIO的编号*/
    gpio_num=of_get_named_gpio(test_device_node,"gpios",0);//从设备树获取到 GPIO 编号
        if(gpio_num<0)
        {
             printk("of_get_named_gpio error!\n");
             return -1；
        }
        printk("gpio_num is :%n\n",gpio_num);
    irq_reg=gpio_request(gpio_num,"beep");//向内核申请指定gpio
        if(irq_reg<0)
            {
                printk("gpio_request error!\n");
                return -1；
            }
    /*配置中断步骤三：设置GPIO方向*/
    gpio_direction_input(gpio_num,1);//这个是设置该GPIO口为输出功能输出高电平是 1
    /*配置中断步骤四：获取中断号，有两种方法*/
    //方法一
    //irq_num=gpio_to_irq(gpio_num);       //从设备树获取到 中断号
    //方法二
    irq_num=irq_of_parse_and_map(test_device_node,0);//从设备树获取中断号 设备树中设置了interrupt属性
    printk("irq is :%d\n",irq_num);     
    /*配置中断步骤五：申请中断*/
    ret=request_irq(irq_num,test_key_IRQ,IRQF_TRLGGER_RISING,"test_key",NULL);   //向内核注册中断号含中断处理函数 参数test_key_IRQ中断处理函数
        if(ret<0)
            {
                printk("ret error!\n");
                return -1；
            }
/*中断下文tasklet于工作队列的实现相关初始化*/
    //tasklet_init(&key_tasklet,tasklet_test,0);//参数一是下文结构体变量、绑定的函数、
    INIT_WORK(&work_struct,tasklet_test);//使用工作队列代替tasklet中断下文

/*注册杂项设备驱动*/
    misc_reg = misc_register(&misc_dev);//注册杂项设备，自动生成设备节点
    if(misc_reg<0)
    {
        printk("misc register is error\n");
        return -1;
    }
    printk("misc register is ok\n");

/*返回值*/
    return 0;
}

int beep_remove(struct platform_device*pdev)
{
    printk("beep_remove byb!\n");
    return 0;
}

const struct platform_device_id beep_idtable={  //优先级更高的命名
    .name="beep_test"
};

/*.driver中的变量，匹配设备树的节点compatible属性*/
const struct of_device_id of_match_table_test[]={  
    {.compatible="test1234"},
    {}

};

struct platform_driver beep_driver={   //通过.name名字与device匹配成功后会执行prode函数
    .probe =beep_probe,
    .remove=beep_remove,
    .driver={                           //该结构体的变量是能够通过自身的变量获取到设备树文件的信息
        .owner = THIS_MODULE,
        .name="beep_test",
        .of_match_table=of_match_table_test
    },
    .id_table = &beep_idtable           //该变量比.driver中的.name优先级要高
};

/*功能实现*/
static int beep_driver_init(void)
{
    int plat_reg=0;
    plat_reg =platform_driver_register(&beep_driver);
    if(plat_reg<0){
        printk("platform_driver_register error!\n");
    }
    printk("platform_driver_register ok!\n");

    return 0;
}

static void beep_driver_exit(void)//退出函数没有返回值
{
    platform_driver_unregister(&beep_driver);
    misc_deregister(&misc_dev);
    gpio_free(gpio_num);//释放GPIO编号
    free_irq(irq_num,NULL);
    cancel_work_sync(&key_work); //释放工作队列  参考链接https://zhuanlan.zhihu.com/p/363272242
    //tasklet_kill(&key_tasklet);//删除tasklet
    printk("byb byb !");  
}     

module_init(beep_driver_init);
module_exit(beep_driver_exit); 
/*驱动的出口与入口函数都在此处*/
MODULE_LICENSE("GPL");
/*申明信息*/
	
/*注意在开发板中加载驱动的时候一定要保持板卡的内核和编译的内核是一致的否则会报错
*查看设备节点：ls /dev/hello_cdev
*/


